"""
认证路由模块
处理用户注册、登录、退出等认证相关功能
"""
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, session
from flask_login import login_user, logout_user, current_user, login_required
from sqlalchemy.exc import IntegrityError

from app import db
from app.models.user import User
from app.auth.decorators import handle_exceptions, validate_schema
from app.auth.utils import (
    hash_password, verify_password, generate_jwt_tokens, 
    generate_reset_token, verify_reset_token, mark_reset_token_used,
    generate_verification_code, validate_verification_code,
    log_auth_attempt, validate_password_strength
)
from app.auth.schemas import (
    LoginSchema, RegisterSchema, ChangePasswordSchema,
    ResetPasswordRequestSchema, ResetPasswordSchema, UpdateProfileSchema
)
from app.auth.exceptions import (
    AuthenticationError, InvalidCredentialsError, UserNotFoundError,
    EmailAlreadyExistsError, UsernameAlreadyExistsError, PasswordTooWeakError
)
from app.utils.helpers import validate_email, validate_username

auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/register', methods=['GET', 'POST'])
@handle_exceptions
def register():
    """
    用户注册
    """
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    
    if request.method == 'GET':
        return render_template('auth/register.html')
    
    # POST请求处理注册
    try:
        # 验证输入数据
        data = request.form
        
        # 基本验证
        if not all([data.get('username'), data.get('email'), data.get('password')]):
            flash('请填写所有必填字段', 'danger')
            return render_template('auth/register.html')
        
        if data.get('password') != data.get('confirm_password'):
            flash('两次输入的密码不一致', 'danger')
            return render_template('auth/register.html')
        
        if not data.get('agree_terms'):
            flash('请同意服务条款', 'danger')
            return render_template('auth/register.html')
        
        # 验证用户名和邮箱格式
        if not validate_username(data['username']):
            flash('用户名格式不正确，只能包含字母、数字、下划线和连字符', 'danger')
            return render_template('auth/register.html')
        
        if not validate_email(data['email']):
            flash('邮箱格式不正确', 'danger')
            return render_template('auth/register.html')
        
        # 验证密码强度
        is_valid, message = validate_password_strength(data['password'])
        if not is_valid:
            flash(message, 'danger')
            return render_template('auth/register.html')
        
        # 检查用户名和邮箱是否已存在
        if User.query.filter_by(username=data['username']).first():
            flash('该用户名已被使用', 'danger')
            return render_template('auth/register.html')
        
        if User.query.filter_by(email=data['email']).first():
            flash('该邮箱已被注册', 'danger')
            return render_template('auth/register.html')
        
        # 创建新用户
        user = User(
            username=data['username'],
            email=data['email']
        )
        user.set_password(data['password'])
        
        db.session.add(user)
        db.session.commit()
        
        # 记录注册成功
        log_auth_attempt(user.id, True, 'register', '用户注册成功')
        
        # 自动登录
        login_user(user, remember=True)
        flash('注册成功！欢迎来到 SteHub！', 'success')
        
        # 重定向到首页或next参数指定的页面
        next_page = request.args.get('next')
        if next_page:
            return redirect(next_page)
        return redirect(url_for('main.index'))
    
    except IntegrityError:
        db.session.rollback()
        flash('注册失败，用户名或邮箱可能已被使用', 'danger')
        return render_template('auth/register.html')
    except Exception as e:
        db.session.rollback()
        log_auth_attempt(None, False, 'register', f'注册失败: {str(e)}')
        flash('注册失败，请稍后重试', 'danger')
        return render_template('auth/register.html')

@auth_bp.route('/login', methods=['GET', 'POST'])
@handle_exceptions
def login():
    """
    用户登录
    """
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    
    if request.method == 'GET':
        return render_template('auth/login.html')
    
    # POST请求处理登录
    try:
        data = request.form
        
        if not all([data.get('username'), data.get('password')]):
            flash('请填写用户名和密码', 'danger')
            return render_template('auth/login.html')
        
        # 查找用户（支持用户名或邮箱登录）
        user = User.query.filter(
            (User.username == data['username']) | (User.email == data['username'])
        ).first()
        
        if not user or not user.check_password(data['password']):
            log_auth_attempt(
                user.id if user else None, 
                False, 
                'login', 
                '用户名或密码错误'
            )
            flash('用户名或密码错误', 'danger')
            return render_template('auth/login.html')
        
        if not user.is_active:
            log_auth_attempt(user.id, False, 'login', '用户账户已被禁用')
            flash('您的账户已被禁用，请联系管理员', 'danger')
            return render_template('auth/login.html')
        
        # 登录成功
        remember_me = bool(data.get('remember_me'))
        login_user(user, remember=remember_me)
        
        # 更新用户状态
        user.update_online_status(True)
        db.session.commit()
        
        log_auth_attempt(user.id, True, 'login', '登录成功')
        
        flash(f'欢迎回来，{user.username}！', 'success')
        
        # 重定向到首页或next参数指定的页面
        next_page = request.args.get('next')
        if next_page:
            return redirect(next_page)
        return redirect(url_for('main.index'))
    
    except Exception as e:
        log_auth_attempt(None, False, 'login', f'登录失败: {str(e)}')
        flash('登录失败，请稍后重试', 'danger')
        return render_template('auth/login.html')

@auth_bp.route('/logout')
@login_required
def logout():
    """
    用户退出登录
    """
    # 更新用户状态
    current_user.update_online_status(False)
    db.session.commit()
    
    log_auth_attempt(current_user.id, True, 'logout', '退出登录')
    
    logout_user()
    flash('您已成功退出登录', 'info')
    return redirect(url_for('main.index'))

@auth_bp.route('/profile', methods=['GET', 'POST'])
@login_required
@handle_exceptions
def profile():
    """
    用户个人资料页面
    """
    if request.method == 'GET':
        return render_template('auth/profile.html')
    
    # POST请求处理资料更新
    try:
        data = request.form
        
        # 更新基本信息
        if data.get('username') and data['username'] != current_user.username:
            if not validate_username(data['username']):
                flash('用户名格式不正确', 'danger')
                return redirect(url_for('auth.profile'))
            
            if User.query.filter_by(username=data['username']).first():
                flash('该用户名已被使用', 'danger')
                return redirect(url_for('auth.profile'))
            
            current_user.username = data['username']
        
        if data.get('email') and data['email'] != current_user.email:
            if not validate_email(data['email']):
                flash('邮箱格式不正确', 'danger')
                return redirect(url_for('auth.profile'))
            
            if User.query.filter_by(email=data['email']).first():
                flash('该邮箱已被使用', 'danger')
                return redirect(url_for('auth.profile'))
            
            current_user.email = data['email']
        
        # 更新其他信息
        if 'bio' in data:
            current_user.bio = data['bio'][:500]  # 限制长度
        
        if 'avatar_url' in data:
            current_user.avatar_url = data['avatar_url']
        
        db.session.commit()
        
        flash('个人资料更新成功', 'success')
        return redirect(url_for('auth.profile'))
    
    except Exception as e:
        db.session.rollback()
        flash('更新失败，请稍后重试', 'danger')
        return redirect(url_for('auth.profile'))

@auth_bp.route('/change-password', methods=['GET', 'POST'])
@login_required
@handle_exceptions
def change_password():
    """
    修改密码
    """
    if request.method == 'GET':
        return render_template('auth/change_password.html')
    
    # POST请求处理密码修改
    try:
        data = request.form
        
        if not all([data.get('current_password'), data.get('new_password'), data.get('confirm_password')]):
            flash('请填写所有密码字段', 'danger')
            return render_template('auth/change_password.html')
        
        # 验证当前密码
        if not current_user.check_password(data['current_password']):
            flash('当前密码错误', 'danger')
            return render_template('auth/change_password.html')
        
        # 验证新密码
        if data['new_password'] != data['confirm_password']:
            flash('两次输入的新密码不一致', 'danger')
            return render_template('auth/change_password.html')
        
        is_valid, message = validate_password_strength(data['new_password'])
        if not is_valid:
            flash(message, 'danger')
            return render_template('auth/change_password.html')
        
        # 更新密码
        current_user.set_password(data['new_password'])
        db.session.commit()
        
        log_auth_attempt(current_user.id, True, 'change_password', '密码修改成功')
        flash('密码修改成功', 'success')
        
        return redirect(url_for('auth.profile'))
    
    except Exception as e:
        db.session.rollback()
        log_auth_attempt(current_user.id, False, 'change_password', f'密码修改失败: {str(e)}')
        flash('密码修改失败，请稍后重试', 'danger')
        return render_template('auth/change_password.html')

@auth_bp.route('/forgot-password', methods=['GET', 'POST'])
@handle_exceptions
def forgot_password():
    """
    忘记密码 - 发送重置邮件
    """
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    
    if request.method == 'GET':
        return render_template('auth/forgot_password.html')
    
    # POST请求处理密码重置请求
    try:
        email = request.form.get('email', '').strip()
        
        if not email:
            flash('请输入邮箱地址', 'danger')
            return render_template('auth/forgot_password.html')
        
        # 查找用户
        user = User.query.filter_by(email=email, is_active=True).first()
        
        if user:
            # 生成重置令牌（在实际应用中应该发送邮件）
            reset_token = generate_reset_token(user.id)
            
            # 这里应该发送邮件，现在只是演示
            reset_url = url_for('auth.reset_password', token=reset_token, _external=True)
            
            # 记录日志
            log_auth_attempt(user.id, True, 'forgot_password', f'重置令牌已生成: {reset_token}')
            
            # 在开发环境中显示重置链接
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return jsonify({
                    'success': True,
                    'message': '重置链接已发送到您的邮箱',
                    'debug_url': reset_url  # 仅在开发环境返回
                })
            else:
                flash('重置链接已发送到您的邮箱（开发模式：请在控制台查看链接）', 'info')
                # 在开发环境中打印重置链接
                print(f"密码重置链接: {reset_url}")
        else:
            # 即使邮箱不存在也显示成功，防止邮箱枚举攻击
            flash('如果该邮箱已注册，重置链接将发送到您的邮箱', 'info')
        
        return render_template('auth/forgot_password.html')
    
    except Exception as e:
        log_auth_attempt(None, False, 'forgot_password', f'密码重置请求失败: {str(e)}')
        flash('请求失败，请稍后重试', 'danger')
        return render_template('auth/forgot_password.html')

@auth_bp.route('/reset-password/<token>', methods=['GET', 'POST'])
@handle_exceptions
def reset_password(token):
    """
    重置密码
    """
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    
    # 验证重置令牌
    user_id = verify_reset_token(token)
    if not user_id:
        flash('重置链接无效或已过期', 'danger')
        return redirect(url_for('auth.forgot_password'))
    
    user = User.query.get(user_id)
    if not user or not user.is_active:
        flash('用户不存在或已被禁用', 'danger')
        return redirect(url_for('auth.forgot_password'))
    
    if request.method == 'GET':
        return render_template('auth/reset_password.html', token=token)
    
    # POST请求处理密码重置
    try:
        data = request.form
        
        if not all([data.get('new_password'), data.get('confirm_password')]):
            flash('请填写所有密码字段', 'danger')
            return render_template('auth/reset_password.html', token=token)
        
        if data['new_password'] != data['confirm_password']:
            flash('两次输入的密码不一致', 'danger')
            return render_template('auth/reset_password.html', token=token)
        
        is_valid, message = validate_password_strength(data['new_password'])
        if not is_valid:
            flash(message, 'danger')
            return render_template('auth/reset_password.html', token=token)
        
        # 更新密码
        user.set_password(data['new_password'])
        
        # 标记重置令牌为已使用
        mark_reset_token_used(token)
        
        db.session.commit()
        
        log_auth_attempt(user.id, True, 'reset_password', '密码重置成功')
        flash('密码重置成功，请使用新密码登录', 'success')
        
        return redirect(url_for('auth.login'))
    
    except Exception as e:
        db.session.rollback()
        log_auth_attempt(user.id, False, 'reset_password', f'密码重置失败: {str(e)}')
        flash('密码重置失败，请稍后重试', 'danger')
        return render_template('auth/reset_password.html', token=token)

@auth_bp.route('/api/token', methods=['POST'])
@handle_exceptions
def get_token():
    """
    获取JWT令牌（API认证）
    """
    data = request.get_json()
    
    if not data or not all([data.get('username'), data.get('password')]):
        return jsonify({
            'error': '缺少用户名或密码',
            'code': 'MISSING_CREDENTIALS'
        }), 400
    
    # 查找用户
    user = User.query.filter(
        (User.username == data['username']) | (User.email == data['username'])
    ).first()
    
    if not user or not user.check_password(data['password']):
        log_auth_attempt(
            user.id if user else None, 
            False, 
            'api_login', 
            'API认证失败'
        )
        return jsonify({
            'error': '用户名或密码错误',
            'code': 'INVALID_CREDENTIALS'
        }), 401
    
    if not user.is_active:
        log_auth_attempt(user.id, False, 'api_login', '用户账户已被禁用')
        return jsonify({
            'error': '用户账户已被禁用',
            'code': 'USER_INACTIVE'
        }), 403
    
    # 生成JWT令牌
    remember_me = data.get('remember_me', False)
    tokens = generate_jwt_tokens(user, remember_me)
    
    log_auth_attempt(user.id, True, 'api_login', 'API认证成功')
    
    return jsonify({
        'success': True,
        'user': {
            'id': user.id,
            'username': user.username,
            'email': user.email,
            'role': user.role
        },
        'tokens': tokens
    })

@auth_bp.route('/api/refresh', methods=['POST'])
@handle_exceptions
def refresh_token():
    """
    刷新JWT令牌
    """
    from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token
    
    try:
        user_id = get_jwt_identity()
        user = User.query.get(user_id)
        
        if not user or not user.is_active:
            return jsonify({
                'error': '用户不存在或已被禁用',
                'code': 'USER_NOT_FOUND'
            }), 401
        
        # 创建新的访问令牌
        new_token = create_access_token(identity=user_id)
        
        return jsonify({
            'success': True,
            'access_token': new_token
        })
    
    except Exception as e:
        return jsonify({
            'error': '令牌刷新失败',
            'code': 'TOKEN_REFRESH_FAILED'
        }), 401

@auth_bp.route('/verify-email')
@login_required
def verify_email():
    """
    邮箱验证（待实现）
    """
    flash('邮箱验证功能开发中', 'info')
    return redirect(url_for('auth.profile'))

@auth_bp.route('/sessions')
@login_required
def sessions():
    """
    登录会话管理
    """
    # 这里可以显示用户的登录会话信息
    flash('会话管理功能开发中', 'info')
    return redirect(url_for('auth.profile'))